#include <stdio.h>
#include "../tdp2/tdp2_threading.hpp"
#include "rphoton.hpp"

using namespace TDP;
using namespace TDP::Math;
using namespace TDP::Threading;
using namespace RDX::ObjectManagement;

extern MultilevelThreadDispatcher *mlDispatcher;
static CString lmBasePath(TDP::Memory::DefaultAllocator());

static Vector<unsigned int> chartLightmap(TDP::Memory::DefaultAllocator());

struct rpFloatLightmap
{
	Vector<FVec3> elements;
	Vector<bool> isSigned;
	unsigned int width;
	unsigned int height;

	inline rpFloatLightmap(unsigned int width, unsigned int height, const Allocator &alloc)
		: elements(alloc),
		  isSigned(alloc)
	{
		this->width = width;
		this->height = height;
		elements.Resize(NULL, width*height, FVec3(0.0f));
		isSigned.Resize(NULL, width*height, false);
	}

	void PlaceSingle(const float *px, bool s, unsigned int x, unsigned int y)
	{
		elements[y*width+x] = FVec3(px) * 255.0f;
		isSigned[y*width+x] = s;
	}

	void PlaceSuperDeluxe(const float *px, unsigned int x, unsigned int y)
	{
		for(unsigned int i=0;i<7;i++)
			PlaceSingle(px + i*3, true, x, y+i*config.inputSubchartSize);
	}

	void PlaceSH(const float *px, unsigned int x, unsigned int y)
	{
		for(unsigned int i=0;i<10;i++)
			PlaceSingle(px + i*3, i != 0, x, y+i*config.inputSubchartSize);
	}

	void PlaceChart(const CString &chart)
	{
		FILE *stream = fopen(chart.Chars(), "rb");
		rpChartHeader header;

		fread(&header, sizeof(header), 1, stream);
		unsigned int numFlagBytes = (header.width*header.height+7)/8;
		Vector<unsigned char> fillBits(TDP::Memory::DefaultAllocator());
		fillBits.Resize(NULL, numFlagBytes);
		fread(fillBits, numFlagBytes, 1, stream);

		unsigned int flagIndex = 0;
		for(unsigned int y=0;y<header.height;y++)
		{
			for(unsigned int x=0;x<header.width;x++,flagIndex++)
			{
				if(fillBits[flagIndex/8] & (1 << (flagIndex&7)))
				{
					float elements[RP_CHARTMODE_MAX_ELEMENTS];
					fread(elements, sizeof(float) * RP_CHARTMODE_ELEMENTS[header.chartMode], 1, stream);

					switch(header.chartMode)
					{
					case RP_CHARTMODE_COLOR:
						this->PlaceSingle(elements, false, header.baseX+x, header.baseY+y);
						break;
					case RP_CHARTMODE_SH:
						this->PlaceSH(elements, header.baseX+x, header.baseY+y);
						break;
					case RP_CHARTMODE_SUPERDELUXE:
						this->PlaceSuperDeluxe(elements, header.baseX+x, header.baseY+y);
						break;
					};
				}
			}
		}

		fclose(stream);
	}

	void ExportToFI(const CString &path)
	{
		FILE *stream = fopen(path.Chars(), "wb");
		fwrite(&width, sizeof(int), 1, stream);
		fwrite(&height, sizeof(int), 1, stream);

		unsigned char bits = 0;
		unsigned int bitOffs = 0;

		for(unsigned int i=0;i<isSigned.Count();i++)
		{
			if(isSigned[i])
				bits |= (1 << bitOffs);
			bitOffs++;

			if(bitOffs == 8)
			{
				fwrite(&bits, 1, 1, stream);
				bits = 0;
				bitOffs = 0;
			}
		}
		if(bitOffs)
			fwrite(&bits, 1, 1, stream);

		// Write elements
		fwrite(elements, sizeof(FVec3) * width * height, 1, stream);

		fclose(stream);
	}

};

void CreateLightmap(int lmID)
{
	rpFloatLightmap lm(config.inputSubchartSize, config.inputSubchartSize * RP_CHARTMODE_ELEMENTS[config.chartMode]/3, TDP::Memory::DefaultAllocator());

	for(unsigned int c=0;c<geometry->numCharts;c++)
	{
		char path[4096];
		sprintf(path, "%s_photons/chart%i.chartdata", lmBasePath.Chars(), c);
		if(chartLightmap[c] == lmID)
		{
			CString pathStr(NULL, TDP::Memory::DefaultAllocator(), path);
			lm.PlaceChart(pathStr);
		}
	}

	char fipath[4096];
	sprintf(fipath, "%s_lm%i_cm%i.fi", lmBasePath.Chars(), lmID, (int)config.chartMode);
	{
		CString fiPathStr(NULL, TDP::Memory::DefaultAllocator(), fipath);
		lm.ExportToFI(fiPathStr);
	}
	printf("Exported %s\n", fipath);
}



class rpCreateLightmapJob : public TDP::Threading::SimpleJob
{
private:
	unsigned int _numLightmaps;
	volatile AtomicInt _currentLightmap;

public:
	rpCreateLightmapJob(unsigned int numLightmaps)
	{
		_numLightmaps = numLightmaps;
		RDX::Threading::NonAtomicWrite(&_currentLightmap, 0);
	}

	void *GetWork()
	{
		NonAtomicInt lmid = RDX::Threading::AtomicIncrement(&_currentLightmap) - 1;

		if(lmid >= _numLightmaps)
			return NULL;

		char *basePtr = reinterpret_cast<char *>(NULL) + 1 + lmid;
		return basePtr;
	}

	void Work(void *ptr)
	{
		int lightmapNo = static_cast<unsigned int>(reinterpret_cast<char *>(ptr) - reinterpret_cast<char *>(NULL) - 1);
		CreateLightmap(lightmapNo);
	}

	void Twiddle()
	{
	}
};


void CreateLightmaps(const CString &basePath)
{
	int objID = 0;
	lmBasePath.Set(NULL, basePath);

	// Figure out which charts belong to which lightmaps
	chartLightmap.Resize(NULL, geometry->numCharts, 0);

	for(unsigned int chartID=0;chartID<geometry->numCharts;chartID++)
	{
		unsigned int lightmapID = 0;
		const ListLink<rpManifold *> *manifoldLL=chartManifolds[chartID].manifoldList.First();

		if(manifoldLL)
			lightmapID = manifoldLL->Data()->seg->lightMapIndex;

		chartLightmap[chartID] = lightmapID;
	}

	rpCreateLightmapJob job(geometry->numLightmaps);
	mlDispatcher->InstallJob(&job, true);
}
